/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
package targets.cpp

import java.io.File;
import commons.*
import org.latestbit.smartobjects.smartidlc.*
import org.latestbit.smartobjects.smartidlc.model.*

/**
 * @author abdulla-a
 */
public class Module extends TargetLangModule {
	def String directoryPrefix; 
	def String nsalias;
	
	def Module() {
		setModuleName("cpp");
	};

	def String getTargetLanguageName() {
		return "C++";
	}
	
	def generateHeaderFileName(namedObject, String prefix="", String postfix="", String extension=".hpp") {
		return prefix+namedObject.getName().replace('.','_')+postfix+extension;
	}
	
	def generateQualifiedTypeName ( IDLDataType idlType, String prefix = "", String postfix = "" ) {
		if(idlType.isImported()) {
			return idlType.userObject.namespace.name.replace('.','_')+"::"+prefix+idlType.relativeName +postfix;
		}
		else {					
			return prefix+idlType.name+postfix;
		}
	}
	
	def generateQualifiedTypeName ( IDLElement element, String prefix = "", String postfix = "" ) {
		if(targetNamespace!= element.namespace) {
			return element.namespace.name.replace(".","_")+"::"+prefix+element.name+postfix;
		}
		else {
			return prefix+element.name+postfix;
		}
	}

	
	def generateQualifiedName ( element, String prefix = "", String postfix = "" ) {
		if(targetNamespace!= element.namespace) {
			return element.namespace.name+"."+prefix+element.name+postfix;
		}
		else {					
			return prefix+element.name+postfix;
		}
	}	
	
	def toCppType(IDLDataType idlType, asMethodParameter = false, disableInterfaces = false, disablePointers = false, asForwardDeclaration = false) {
		if(idlType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME )) {
			if( !(idlType.userObject instanceof IDLTypeDef) && asForwardDeclaration ) {
				return "class "+ toCppType( idlType, asMethodParameter, disableInterfaces, disablePointers, false );
			}
			else {
				if ( idlType.userObject instanceof IDLTypeDef &&
					!idlType.userObject.refDataType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME)) {
					return idlType.name+ (disablePointers ? "": "*");
				}
				else {
					if(!disableInterfaces)
						return generateQualifiedTypeName ( idlType, "I" ) + (disablePointers ? "": "*");
					else
						return generateQualifiedTypeName ( idlType ) + (disablePointers ? "": "*");
				}
			}
		}
		if(idlType.typeId.equals(IDLDataType.DataTypeId.PRIMITIVE ))
			return toPrimitiveCppType(idlType.primitiveType, asMethodParameter );		
		if(idlType.typeId.equals(IDLDataType.DataTypeId.COMPLEX ))
			return toComplexCppType(idlType.complexType, asMethodParameter, disableInterfaces, asForwardDeclaration);
	}
	
	def toCppTypeEx(IDLDataType idlType, flags = [:] as Map) {
		boolean asMethodParameter = false, disableInterfaces = false, disablePointers = false, asForwardDeclaration = false;
		asMethodParameter = flags["asMethodParameter"];
		disableInterfaces = flags["disableInterfaces"];
		disablePointers = flags["disablePointers"];
		asForwardDeclaration = flags["asForwardDeclaration"];
		return toCppType(idlType, asMethodParameter, disableInterfaces, disablePointers, asForwardDeclaration);
	}
	
	def toPrimitiveCppType(IDLPrimitiveType primitiveType, asMethodParameter = false) {
		switch ( primitiveType.typeId ) {
			case IDLPrimitiveType.TypeId.BOOLEAN:
				return "bool";
				break;
			case IDLPrimitiveType.TypeId.BYTE:
				return "char";
				break;			
			case IDLPrimitiveType.TypeId.UNSIGNED_BYTE:
				return "unsigned char";
				break;
			case IDLPrimitiveType.TypeId.CHAR:
				return "char";
				break;		
			case IDLPrimitiveType.TypeId.WCHAR:
				return "wchar_t";
				break;						
			case IDLPrimitiveType.TypeId.DOUBLE:
				return "double";
				break;
			case IDLPrimitiveType.TypeId.FLOAT:
				return "float";
				break;
			case IDLPrimitiveType.TypeId.INT16:
				return "boost::int16_t";
				break;						
			case IDLPrimitiveType.TypeId.UINT16:
				return "boost::uint16_t";
				break;
			case IDLPrimitiveType.TypeId.INT32:
				return "boost::int32_t";
				break;						
			case IDLPrimitiveType.TypeId.UINT32:
				return "boost::uint32_t";
				break;
			case IDLPrimitiveType.TypeId.INT64:
				return "boost::int64_t";
				break;													
			case IDLPrimitiveType.TypeId.UINT64:
				return "boost::uint64_t";
				break;														
			case IDLPrimitiveType.TypeId.BIGINTEGER:
				return "BigInteger" + (asMethodParameter ? "&":"");				
				break;
			case IDLPrimitiveType.TypeId.BIGDECIMAL:
				return "BigDecimal" + (asMethodParameter ? "&":"");				
				break;														
			case IDLPrimitiveType.TypeId.STRING:
				return "std::string" + (asMethodParameter ? "&":"");
				break;
			case IDLPrimitiveType.TypeId.WSTRING:				
				return "std::wstring" + (asMethodParameter ? "&":"");
				break;
			case IDLPrimitiveType.TypeId.DATETIME:				
				return "time_t";
				break;				
		}
	}
	
	def toPrimitiveCppType(IDLPrimitiveType.TypeId primitiveTypeId, asMethodParameter = false) {
		IDLPrimitiveType type = new IDLPrimitiveType();
		type.typeId = primitiveTypeId;
		return toPrimitiveCppType(type, asMethodParameter);
	}
				
	def toComplexCppType(IDLComplexType complexType, asMethodParameter = false, disableInterfaces = false, asForwardDeclaration = false) {
		switch ( complexType.complexTypeId ) {
			case IDLComplexType.ComplexTypeId.ARRAY:
				return "std::vector< ${toCppType(complexType.complexItemDataType, false, disableInterfaces, false, asForwardDeclaration)} > " + (asMethodParameter ? "&":"");
			case IDLComplexType.ComplexTypeId.LIST:
				return "std::list< ${toCppType(complexType.complexItemDataType, false, disableInterfaces, false, asForwardDeclaration)} > " + (asMethodParameter ? "&":"");
				break;
			case IDLComplexType.ComplexTypeId.MAP:
				return "std::map< ${toPrimitiveCppType(complexType.complexKeyDataType)}, ${toCppType(complexType.complexItemDataType, false, disableInterfaces, false, asForwardDeclaration)} > " + (asMethodParameter ? "&":"");
				break;				
		}
	}
	
	def isTypePassByRef(IDLDataType idlType) {
		if(idlType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME )) {
			if ( idlType.userObject instanceof IDLTypeDef &&
			!idlType.userObject.refDataType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME)) {
				return false;
			}
		}
		if(idlType.typeId.equals(IDLDataType.DataTypeId.PRIMITIVE )) {
			if(idlType.primitiveType.typeId != IDLPrimitiveType.TypeId.BIGINTEGER && 
			   idlType.primitiveType.typeId != IDLPrimitiveType.TypeId.BIGDECIMAL &&
			   idlType.primitiveType.typeId != IDLPrimitiveType.TypeId.STRING &&
			   idlType.primitiveType.typeId != IDLPrimitiveType.TypeId.WSTRING ) {
				return false;
			}
		}		
		return true;
	}
	
	def isComplexTypeOfPointer(IDLDataType idlType) {
		if(idlType.typeId.equals(IDLDataType.DataTypeId.COMPLEX )) {
			if(idlType.complexType.complexItemDataType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME)) {
				return true;
			}
			if(idlType.complexType.complexItemDataType.typeId.equals(IDLDataType.DataTypeId.COMPLEX)) {
				return isComplexTypeOfPointer(idlType.complexType.complexItemDataType);
			}
		}
		return false;
	}
	
	def isQNPointer(IDLDataType idlType) {
		if(idlType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME )) {
			return true;
		}
		else
			return false;
	}
	
	def toImportingQN(IDLDataType idlType, def prefix="", def postfix="") {
		if(idlType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME )) {
			if(idlType.isImported()) {
				return "<"+prefix+idlType.relativeName+postfix+".hpp>";
			}
			else
				return "\""+prefix+idlType.name+postfix+".hpp\"";
		}
		else
		if(idlType.typeId.equals(IDLDataType.DataTypeId.COMPLEX )) {
			return toImportingQN(idlType.complexType.complexItemDataType, prefix, postfix);
		}
	}
	
	def toImportingQN(IDLElement element, def prefix="", def postfix="") {
		if(targetNamespace!= element.namespace) {
			return "<"+prefix+element.name+postfix+".hpp>";
		}
		else {
			return "\""+prefix+element.name+postfix+".hpp\"";
		}
	}

	
	def isNeedToInitType(IDLDataType idlType) {
		if(isQNPointer(idlType))
			return true;
		else {
			if(idlType.typeId.equals(IDLDataType.DataTypeId.PRIMITIVE )) {
				switch ( idlType.primitiveType.typeId ) {
					case IDLPrimitiveType.TypeId.BOOLEAN:
					case IDLPrimitiveType.TypeId.DATETIME:
					case IDLPrimitiveType.TypeId.BYTE:
					case IDLPrimitiveType.TypeId.UNSIGNED_BYTE:
					case IDLPrimitiveType.TypeId.CHAR:
					case IDLPrimitiveType.TypeId.WCHAR:
					case IDLPrimitiveType.TypeId.DOUBLE:
					case IDLPrimitiveType.TypeId.FLOAT:
					case IDLPrimitiveType.TypeId.INT16:
					case IDLPrimitiveType.TypeId.UINT16:
					case IDLPrimitiveType.TypeId.INT32:
					case IDLPrimitiveType.TypeId.UINT32:
					case IDLPrimitiveType.TypeId.INT64:
					case IDLPrimitiveType.TypeId.UINT64:
						return true;
						break;
					default:
						return false;
				}
			}
			else
				return false;
		}
	}
	
	def getInitForType(IDLDataType idlType) {
		if(isQNPointer(idlType)) {
			return "NULL";
		}
		else {
			switch ( idlType.primitiveType.typeId ) {
				case IDLPrimitiveType.TypeId.BOOLEAN:
					return "false";
					break;
				case IDLPrimitiveType.TypeId.DATETIME:
					return "(time_t)0";
					break;					
				case IDLPrimitiveType.TypeId.BYTE:
				case IDLPrimitiveType.TypeId.UNSIGNED_BYTE:
				case IDLPrimitiveType.TypeId.CHAR:
				case IDLPrimitiveType.TypeId.WCHAR:
				case IDLPrimitiveType.TypeId.DOUBLE:
				case IDLPrimitiveType.TypeId.FLOAT:			
				case IDLPrimitiveType.TypeId.INT16:
				case IDLPrimitiveType.TypeId.UINT16:
				case IDLPrimitiveType.TypeId.INT32:
				case IDLPrimitiveType.TypeId.UINT32:
				case IDLPrimitiveType.TypeId.INT64:
				case IDLPrimitiveType.TypeId.UINT64:
					return "0";
					break;				
				default:
					return false;
			}			
		}
	}
	
	def generateFieldDestructor(IDLDataType idlType, String fieldName) {
		def subBindings = [ "idlType" : idlType ];
		subBindings+= [ "fieldName" : fieldName ];
				
		if(idlType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME )) {
			return processSubTemplate("types/initializers/qualified_name_field_destructor", subBindings);
		}
		if( isComplexTypeOfPointer(idlType) ) {
			if(idlType.complexType.complexTypeId!= IDLComplexType.ComplexTypeId.MAP ) {
				return processSubTemplate("types/initializers/array_list_field_destructor", subBindings);
			}
			else
				return processSubTemplate("types/initializers/map_field_destructor", subBindings);
		}
		return "";
	}
	
	def generateFieldCopier(IDLDataType idlType, String fieldName, String from) {
		def subBindings = [ "idlType" : idlType ];
		subBindings+= [ "fieldName" : fieldName ];
		subBindings+= [ "fieldFromName" : from ];

		if(idlType.typeId.equals(IDLDataType.DataTypeId.PRIMITIVE )) {
			return processSubTemplate("types/initializers/primitive_field_copier", subBindings);
		}
		else
		if(idlType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME )) {
			return processSubTemplate("types/initializers/qualified_name_field_copier", subBindings);
		}
		else
		if(idlType.typeId.equals(IDLDataType.DataTypeId.COMPLEX )) {
			if( isComplexTypeOfPointer(idlType) ) {
				if(idlType.complexType.complexTypeId!= IDLComplexType.ComplexTypeId.MAP )
					return processSubTemplate("types/initializers/array_list_field_of_pointers_copier", subBindings);
				else
					return processSubTemplate("types/initializers/map_field_of_pointers_copier", subBindings);
			}
			else
				return processSubTemplate("types/initializers/complex_type_field_copier", subBindings);
		}		
		return "";
	}
	
	def isNullable( IDLDataType idlType ) {
		if(idlType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME )) {
			return true;
		}
		if(idlType.typeId.equals(IDLDataType.DataTypeId.PRIMITIVE ))
			return isNullable(idlType.primitiveType);
		if(idlType.typeId.equals(IDLDataType.DataTypeId.COMPLEX ))
			return false;
		return false;
	}
	
	def isNullable( IDLPrimitiveType primitiveType ) {
		return false;
	}
	
	def toResultCppType ( IDLInterfaceOperation oper, asMethodParameter = false ) {
		if(oper.resultType==null) {
			return "void";
		}
		else {
			if ( ! isServerGenerationMode() || !oper.interfaceReferenceResult ) {
				if( !isServerGenerationMode() && oper.isResultSessionId() ) {
					return "SessionId < "+toCppType(oper.resultType)+" > *";
				}
				else {
					boolean isConst =  ( !isQNPointer(oper.resultType) || isServerGenerationMode() ) && asMethodParameter;
					return ( isConst ? "const " : "")+ toCppType(oper.resultType, asMethodParameter);
				}
			}
			else {
				return generateQualifiedTypeName ( oper.resultType, "", "Skeleton" )+"*";
			}
		}
	}
	
	def toParamCppType ( IDLInterfaceOperationParam param ) {
		if ( isServerGenerationMode() || !param.isInterfaceReference() ) {
			if(!isServerGenerationMode() && param.isSessionId())
				return "SessionId < "+toCppType(param.type)+" > *";
			else {
				boolean isConst = !isServerGenerationMode() || !isQNPointer(param.type); 
				return ( isConst ? "const " : "")+toCppType(param.type, true);
			}
		}
		else {
			return generateQualifiedTypeName ( param.type, "", "Skeleton" )+"*";
		}
	}
	
	def toCppFullName( IDLElement element ) {
		String result = element.getName(); 
		if(element.getNamespace()!=null)
			result = element.getNamespace().getName().replace(".","_") + "::" +result;
		return result;		
	}
	
	def parseTargetArgs() {
		if(this.targetArgs!=null) {
			this.directoryPrefix = this.targetArgs.get("dirpfx");
			if(this.directoryPrefix!=null) {
				reporter.onVerboseMessage("Directory prefix: "+ this.directoryPrefix);
			}
			this.nsalias = this.targetArgs.get("nsalias");
			if(this.nsalias!=null) {
				reporter.onVerboseMessage("Namespace alias defined: "+ this.nsalias);
			}
		}
	}
	
	def getDirectoryPrefix() {
		if( this.directoryPrefix!=null )
			return this.directoryPrefix+File.separator;
		else
			return "";
	}
	
	def getNamespaceAlias() {
		return this.nsalias;
	}
		
	def generateDirectories() {
		def parts = this.targetNamespace.name.split("\\.");
		def cppOutputDir = this.outputDir + getDirectoryPrefix();
		def dir = new File ( outputDir );
		if (!dir.exists()) {
			if(!dir.mkdir()) {
				throw new IDLTargetGenerationException("Unable to create directory: ${dir.absolutePath}");
			}
		}
		dir = new File ( cppOutputDir );
		if (!dir.exists()) {
			if(!dir.mkdir()) {
				throw new IDLTargetGenerationException("Unable to create directory: ${dir.absolutePath}");
			}
		}
				
		this.outputDir = cppOutputDir;
		reporter.onMessage("--> C++ output dir: ${cppOutputDir}");
	}
	
	def generateTargetNamespace() {
		def binding = [:] as Map;
		processTemplate ("base/namespace_main_header_file", binding, generateHeaderFileName(this.targetNamespace));				
	}		
	
	def generateTypeDef ( typeDef ) {
		def binding = [ "typeDef" : typeDef ];

		if ( !typeDef.refDataType.typeId.equals(IDLDataType.DataTypeId.QUALIFIED_NAME) || typeDef.refDataType.isEncrypted() ) {
			processTemplate ("types/typedef_as_class_interface", binding, generateHeaderFileName(typeDef, "I"));
			processTemplate ("types/typedef_as_class_impl", binding, generateHeaderFileName(typeDef));			
		}
		else {
			processTemplate ("types/typedef_as_keyword_interface", binding, generateHeaderFileName(typeDef, "I"));
			processTemplate ("types/typedef_as_keyword", binding, generateHeaderFileName(typeDef));
		}				
	}
	
	def generateStructure ( structure ) {
		def binding = [ "structure" : structure ];
		processTemplate ("types/structure_interface", binding, generateHeaderFileName(structure, "I"));
		processTemplate ("types/structure_impl_hpp", binding, generateHeaderFileName(structure));
		processTemplate ("types/structure_impl_inl", binding, generateHeaderFileName(structure,"","",".inl"));
	}
	
	def generateChoice ( choice ) {
		def binding = [ "choice" : choice ];
		processTemplate ("types/choice_interface", binding, generateHeaderFileName(choice, "I"));
		processTemplate ("types/choice_impl_hpp", binding, generateHeaderFileName(choice));
		processTemplate ("types/choice_impl_inl", binding, generateHeaderFileName(choice,"","",".inl"));
	}
		
	def generateEnum ( enumeration ) {
		def binding = [ "enumeration" : enumeration ];
		processTemplate ("types/enum_interface", binding, generateHeaderFileName(enumeration, "I"));
		processTemplate ("types/enum_impl", binding, generateHeaderFileName(enumeration));
	}
		
	def generateException ( exc ) {
		def binding = [ "exc" : exc ];
		processTemplate ("types/exception_impl", binding, generateHeaderFileName(exc));
	}	
	
	def generateInterface ( iface ) {
		def binding = [ "iface" : iface ];
		processTemplate ("types/client_interface", binding, generateHeaderFileName(iface, "I"));
		processTemplate ("types/client_callback_interface", binding, generateHeaderFileName(iface, "I", "Callback"));
		processTemplate ("types/client_interface_impl", binding, generateHeaderFileName(iface));
		this.setServerGenerationMode ( true );
		try {
			processTemplate ("types/server_async_interface", binding, generateHeaderFileName(iface, "I", "SmartObject"));
			processTemplate ("types/server_sync_interface", binding, generateHeaderFileName(iface,"I", "SyncSmartObject"));
			processTemplate ("types/server_async_callback_interface", binding, generateHeaderFileName(iface, "I", "ServerCallback"));
			processTemplate ("types/server_async_callback_interface_impl", binding, generateHeaderFileName(iface, "", "ServerCallback"));
			processTemplate ("types/server_async_skeleton", binding, generateHeaderFileName(iface, "", "Skeleton"));				
			processTemplate ("types/server_sync_skeleton", binding, generateHeaderFileName(iface, "", "SyncSkeleton"));
		}
		finally {
			this.setServerGenerationMode ( false );
		}
	}	
}
